package com.sunxiansheng.redis.util;

import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * Description: Guava本地缓存工具类
 * @Author sun
 * @Create 2024/7/22 12:55
 * @Version 1.0
 */
@Component
@Slf4j
public class CacheUtil<K, V> {

    // 读取配置文件，支持动态刷新（需要配置Nacos）
    @Value("${guaca.cache.switch:false}")
    private Boolean cacheEnabled;

    @Value("${guaca.cache.maximumSize:5000}")
    private int maximumSize;

    @Value("${guaca.cache.expireAfterAccess:3}")
    private int expireAfterAccess;

    /**
     * 缓存key连接符
     */
    private static final String CACHE_KEY_SEPARATOR = "_";

    // 初始化一个Guava缓存
    private final Cache<String, String> localCache = CacheBuilder.newBuilder()
            .maximumSize(maximumSize)
            .expireAfterAccess(expireAfterAccess, TimeUnit.SECONDS)
            .build();

    /**
     * 使用本地缓存获取结果（批量）
     *
     * @param skuIdList id列表
     * @param cachePrefix 查询的前缀（缓存的key由前缀 + "_" + id构成）
     * @param clazz 查询完结果后要反序列化的类型
     * @param function 不走缓存查询结果的逻辑
     * @return 查询结果的Map
     */
    public Map<K, V> getResult(List<K> skuIdList, String cachePrefix, Class<V> clazz, Function<List<K>, Map<K, V>> function) {
        // 判空
        if (CollectionUtils.isEmpty(skuIdList)) {
            return Collections.emptyMap();
        }

        Map<K, V> resultMap = new HashMap<>(16);

        // 如果缓存开关未开启，直接使用function查询
        if (Boolean.FALSE.equals(cacheEnabled)) {
            return function.apply(skuIdList);
        }

        // 记录未命中缓存的ID
        List<K> noCacheIdList = new LinkedList<>();

        // 查Guava本地缓存
        for (K skuId : skuIdList) {
            String cacheKey = generateCacheKey(cachePrefix, skuId);
            String content = localCache.getIfPresent(cacheKey);
            if (StringUtils.isNotBlank(content)) {
                V v = JSON.parseObject(content, clazz);
                resultMap.put(skuId, v);
            } else {
                noCacheIdList.add(skuId);
            }
        }

        // 记录未命中缓存的日志
        if (!CollectionUtils.isEmpty(noCacheIdList)) {
            log.debug("Cache miss for keys: {}", noCacheIdList);
        }

        // 如果所有ID都命中缓存，直接返回结果
        if (CollectionUtils.isEmpty(noCacheIdList)) {
            return resultMap;
        }

        // 使用function查询未命中缓存的ID
        Map<K, V> noCacheResultMap = function.apply(noCacheIdList);
        if (!CollectionUtils.isEmpty(noCacheResultMap)) {
            for (Map.Entry<K, V> entry : noCacheResultMap.entrySet()) {
                K skuId = entry.getKey();
                V content = entry.getValue();
                resultMap.put(skuId, content);
                String cacheKey = generateCacheKey(cachePrefix, skuId);
                localCache.put(cacheKey, JSON.toJSONString(content));
            }
        }

        return resultMap;
    }

    /**
     * 使用本地缓存获取单个结果
     *
     * @param key 缓存的键
     * @param cachePrefix 查询的前缀（缓存的key由前缀 + "_" + key构成）
     * @param clazz 查询完结果后要反序列化的类型
     * @param function 不走缓存查询结果的逻辑
     * @return 查询结果
     * @throws ExecutionException
     */
    public V getSingleResult(K key, String cachePrefix, Class<V> clazz, Function<K, V> function) throws ExecutionException {
        String cacheKey = generateCacheKey(cachePrefix, key);
        String content = localCache.getIfPresent(cacheKey);
        if (StringUtils.isNotBlank(content)) {
            return JSON.parseObject(content, clazz);
        }

        V result = function.apply(key);
        if (result != null) {
            localCache.put(cacheKey, JSON.toJSONString(result));
        }

        return result;
    }

    /**
     * 清除单个缓存
     *
     * @param key 缓存的键
     * @param cachePrefix 查询的前缀（缓存的key由前缀 + "_" + key构成）
     */
    public void invalidateSingleCache(K key, String cachePrefix) {
        String cacheKey = generateCacheKey(cachePrefix, key);
        localCache.invalidate(cacheKey);
        log.debug("Cache invalidated for key: {}", cacheKey);
    }

    /**
     * 清除多个缓存
     *
     * @param keys 缓存的键列表
     * @param cachePrefix 查询的前缀（缓存的key由前缀 + "_" + key构成）
     */
    public void invalidateMultipleCaches(List<K> keys, String cachePrefix) {
        List<String> cacheKeys = new ArrayList<>();
        for (K key : keys) {
            String cacheKey = generateCacheKey(cachePrefix, key);
            cacheKeys.add(cacheKey);
            log.debug("Cache invalidated for key: {}", cacheKey);
        }
        localCache.invalidateAll(cacheKeys);
    }

    /**
     * 清除所有缓存
     */
    public void invalidateAllCaches() {
        localCache.invalidateAll();
        log.debug("All caches invalidated.");
    }

    /**
     * 生成缓存键
     *
     * @param prefix 缓存前缀
     * @param id 缓存ID
     * @return 缓存键
     */
    private String generateCacheKey(String prefix, K id) {
        return prefix + CACHE_KEY_SEPARATOR + id;
    }
}